package com.wechat.page.chat.content;

import java.io.File;
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;

import org.springframework.beans.factory.annotation.Autowired;

import com.swak.codec.Encodes;
import com.swak.entity.Page;
import com.swak.entity.Parameters;
import com.swak.fx.support.AbstractPage;
import com.swak.fx.support.FXMLView;
import com.swak.fx.support.ui.UI;
import com.swak.fx.support.ui.UIScrollPaneInner;
import com.swak.ui.Event;
import com.swak.ui.EventListener;
import com.swak.ui.Events;
import com.swak.utils.Lists;
import com.swak.utils.Maps;
import com.swak.utils.StringUtils;
import com.swak.utils.time.DateTimes;
import com.wechat.Cons;
import com.wechat.dto.ChatDTO;
import com.wechat.dto.ChatRoomDTO;
import com.wechat.dto.ContactDTO;
import com.wechat.dto.MessageDTO;
import com.wechat.enums.ContactType;
import com.wechat.enums.MessageType;
import com.wechat.face.Faces;
import com.wechat.login.Login;
import com.wechat.login.LoginService;
import com.wechat.msg.FileMsg;
import com.wechat.msg.TxtMsg;
import com.wechat.page.api.ChatRoomApi;
import com.wechat.page.api.ContactApi;
import com.wechat.page.api.MessageApi;
import com.wechat.page.chat.content.chatting.ChattingInputComp;
import com.wechat.page.chat.message.MessageComp;
import com.wechat.page.chat.misc.Loading;
import com.wechat.page.chat.misc.TimeDivide;

import javafx.beans.binding.Bindings;
import javafx.beans.property.ListProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleListProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.ListChangeListener.Change;
import javafx.fxml.FXML;
import javafx.geometry.Pos;
import javafx.scene.control.ScrollPane.ScrollBarPolicy;
import javafx.scene.input.Dragboard;
import javafx.scene.input.TransferMode;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.Priority;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.scene.shape.Rectangle;
import lombok.Getter;
import lombok.Setter;
import lombok.experimental.Accessors;

/**
 * ChattingPage.
 * 
 * @author 618lf
 */
@FXMLView(title = "聊天室", value = "ChattingPage.fxml", css = "ChattingPage.css")
public class ChattingPage extends AbstractPage implements EventListener {

	private final String CHATS_STYLE_CLASS = "chatting-page__chats";
	private final String CHAT_STYLE_CLASS = "chatting-page__chat";
	private final String INPUT_STYLE_CLASS = "chatting-page__input";
	private final String MASK_STYLE_CLASS = "chatting-page__mask";

	@FXML
	private GridPane content;

	// chats -- UIScrollPane 拖拉自适应不能缩小，先替换成原生 UIScrollPaneInner
	private UIScrollPaneInner chatsScroll;
	private VBox messages;
	private Loading loading;

	// input
	private ChattingInputComp chattingInput;

	// mask
	private Rectangle mask = new Rectangle();

	@Autowired
	private MessageApi messageApi;
	@Autowired
	private ChatRoomApi roomApi;
	@Autowired
	private ContactApi contactApi;

	public ChattingPage() {

		// chats
		this.chatsScroll = new UIScrollPaneInner();
		this.messages = new VBox();
		this.messages.getStyleClass().add(CHATS_STYLE_CLASS);
		this.messages.setFillWidth(true);
		this.messages.setAlignment(Pos.CENTER);
		this.chatsScroll.setContent(messages);
		this.chatsScroll.setFitToWidth(true);
		this.chatsScroll.setHbarPolicy(ScrollBarPolicy.NEVER);
		this.chatsScroll.setVbarPolicy(ScrollBarPolicy.AS_NEEDED);

		this.loading = new Loading(this.chatsScroll);
		this.content.getChildren().add(this.chatsScroll);
		GridPane.setConstraints(this.chatsScroll, 0, 0);
		GridPane.setVgrow(this.chatsScroll, Priority.ALWAYS);
		GridPane.setHgrow(this.chatsScroll, Priority.ALWAYS);
		GridPane.setFillHeight(this.chatsScroll, true);
		GridPane.setFillWidth(this.chatsScroll, true);

		// input
		this.chattingInput = this.NewComp(ChattingInputComp.class);
		this.chattingInput.getRoot().getStyleClass().add(INPUT_STYLE_CLASS);
		this.content.getChildren().add(this.chattingInput.getRoot());
		GridPane.setConstraints(this.chattingInput.getRoot(), 0, 1);
		GridPane.setHgrow(this.chattingInput.getRoot(), Priority.ALWAYS);
	}

	@Override
	public void initializeParts() {
		super.initializeParts();
		this.mask.getStyleClass().add(MASK_STYLE_CLASS);
		this.addChild(this.mask);
	}

	@Override
	@SuppressWarnings("unchecked")
	public void setupEventHandlers() {
		super.setupEventHandlers();
		this.chattingInput.$on(com.wechat.enums.Events.Event_Send.name(), (p) -> this.doSendMessage((List<Object>) p));
		this.root.setOnDragEntered((e) -> {
			TransferMode mode = e.getTransferMode();
			if (mode == TransferMode.MOVE) {
				this.mask.setWidth(this.content.getWidth());
				this.mask.setHeight(this.content.getHeight());
				this.mask.setVisible(true);
			}
		});
		this.root.setOnDragExited((e) -> {
			this.mask.setVisible(false);
		});
		this.root.setOnDragOver((e) -> {
			e.acceptTransferModes(TransferMode.COPY_OR_MOVE);
		});
		this.root.setOnDragDropped((e) -> {
			TransferMode mode = e.getAcceptedTransferMode();
			if (mode == TransferMode.MOVE) {
				e.setDropCompleted(true);
				Dragboard dragboard = e.getDragboard();
				this.doDrag(dragboard);
			}
		});
	}

	private void doDrag(Dragboard dragboard) {
		if (LOGGER.isDebugEnabled()) {
			LOGGER.debug("[聊天内容] - 移动复制");
		}
	}

	@Override
	public void setupBindings() {
		super.setupBindings();
		this.chattingInput.chatProperty().bind(this.chatProperty());
		this.initProperty.bind(Bindings.createStringBinding(() -> {
			ChatDTO chat = this.chatProperty().get();
			return chat != null ? chat.getId() : StringUtils.EMPTY;
		}, this.chatProperty()));
	}

	// ********************* 事件监听 ************************
	@Override
	public void listen(Event event) {

		if (LOGGER.isDebugEnabled()) {
			LOGGER.debug("[聊天室] - 收到事件 ：{}", event);
		}

		// 收到消息
		if (Cons.Event_Msg_Recv.equals(event.getKey())) {
			this.$runUI(() -> this.onMessageRecv(event.getMessage()));
		}
	}

	private void onMessageRecv(MessageDTO message) {

		// 当前聊天室
		ChatDTO chat = chatProperty.get();
		if (!chat.getId().equals(message.getTalker())) {
			return;
		}

		// 是我的聊天室的消息
		this.addNewestMessage(message);
	}

	// ********************* 发送消息 ************************

	private void doSendMessage(List<Object> messages) {
		StringBuilder message = new StringBuilder();
		for (Object msg : messages) {

			// 文本
			if (msg instanceof String) {
				message.append(msg);
				continue;
			}

			// 非文本先发送现有的文本内容
			if (!message.isEmpty()) {
				this.doSendTxtMessage(message.toString());
				message = new StringBuilder();
			}

			// 文件
			if (msg instanceof File) {
				this.doSendFileMessage((File) msg);
			}
		}

		if (!message.isEmpty()) {
			this.doSendTxtMessage(message.toString());
		}
	}

	// 发送文件消息
	private void doSendFileMessage(File file) {

		// 聊天
		ChatDTO chat = chatProperty.get();

		// 文件名称
		String name = Encodes.urlDecode(file.getName());

		// 文件路径
		String path = file.getPath();

		// 聊天消息
		MessageDTO message = MessageDTO.of()// 创建消息实体
				.setTalker(chat.getId())// 聊天是ID
				.setMsgType(MessageType.File.getV())// 消息类型
				.setMsgBrief(MessageType.File.getDescribe() + name)// 消息简述
				.setMsgContent(FileMsg.of().setName(name) // 文件名称
						.setPath(path) // 文件路径
						.setSize(file.length()).toXml());// 消息内容

		// 异步提交数据
		this.$run(() -> this.doSendMessage(message));
	}

	// 发送文本消息
	private void doSendTxtMessage(String text) {

		// 聊天
		ChatDTO chat = chatProperty.get();

		// 消息
		Login login = LoginService.login();

		// 聊天消息
		MessageDTO message = MessageDTO.of()// 创建消息实体
				.setTalker(chat.getId())// 聊天是ID
				.setMsgType(MessageType.Txt.getV())// 消息类型
				.setMsgBrief(Faces.parseBriefString(text))// 消息简述
				.setMsgContent(
						TxtMsg.of().setContent(text).setTalker(login.getId()).setNickName(login.getNickName()).toXml());// 消息内容

		// 异步提交数据
		this.$run(() -> this.doSendMessage(message));
	}

	private void doSendMessage(MessageDTO message) {
		this.messageApi.prepare(message).thenAccept(this::onSendMessageResult);
	}

	private void onSendMessageResult(MessageDTO message) {
		this.addNewestMessage(message);
		Events.post(Event.of().setKey(Cons.Event_Msg_Send).setMessage(message));
	}

	// ***************************************************

	@Override
	public void setupValueChangedListeners() {
		super.setupValueChangedListeners();

		// 加载联系人数据
		this.initProperty.addListener(this::onInit);
		this.contactProperty.addListener((ob, o, n) -> this.onLoadContact(n));
		this.chatroomProperty.addListener((ob, o, n) -> this.onLoadContacts(n));

		// 加载列表数据
		this.messagesProperty().addListener(messagesChangeListener);
		this.loading.loadingProperty().addListener((ob, o, n) -> {
			if (n) {
				this.$setTimeOut(() -> {
					this.onNextPage();
				}, 1000);
			}
		});
	}

	// ================================================================================
	// 加载数据
	// ================================================================================

	private void onInit(ObservableValue<? extends String> ob, String unActive, String active) {
		if (StringUtils.isBlank(active)) {
			return;
		}

		// 加载联系人
		this.onChatChange(ChatDTO.of().setId(active));
	}

	private void onChatChange(ChatDTO chat) {
		if (chat != null) {
			this.$run(() -> contactApi.get(chat.getId()).thenAccept(contact -> this.contactProperty.set(contact)));
		}
	}

	private void onLoadContact(ContactDTO contact) {
		if (contact == null) {
			return;
		}

		// 查询聊天室里面的所有人(聊天室)
		if (contact.getType() != null && contact.getType() == ContactType.Chat_Room.getType()) {
			this.onLoadChatRoom(contact);
		}

		// 直接加载消息(非聊天室)
		else {
			this.onLoadMessages(contact.getId());
		}
	}

	private void onLoadChatRoom(ContactDTO contact) {
		this.$run(() -> roomApi.get(contact.getId()).thenAccept(room -> this.chatroomProperty.set(room)));
	}

	private void onLoadContacts(ChatRoomDTO room) {
		if (room == null) {
			return;
		}
		String[] userNames = StringUtils.split(room.getUserNames(), Cons.UserNamesSeparateChar);
		this.$runContextCompleteWithUI(() -> this.contactApi.contacts(Lists.newArrayList(userNames))
				.thenAccept(cs -> this.contactsProperty.addAll(cs)))
				.thenAccept((res) -> this.onLoadMessages(room.getId()));
	}

	private void onLoadMessages(String talker) {

		// 开启新的任务
		this.$runUICompleteWithContext(() -> {
			Query query = new Query().setTalker(talker);
			this.param = new Parameters();
			this.lastTimeStamp = null;
			this.lastShowTimeStamp = null;
			this.messages.setVisible(false);
			this.loading.reset();
			this.messagesProperty().removeListener(messagesChangeListener);
			this.messagesProperty().clear();
			this.messages.getChildren().clear();
			this.messagePanes.values().forEach((node) -> {
				MessageComp messageThumb = (MessageComp) node.getProperties().get(UI.Conts.Comp_Key);
				messageThumb.destory();
			});
			this.messagePanes.clear();
			this.messagesProperty().addListener(messagesChangeListener);
			this.messages.setVisible(true);
			return query;
		}).thenCompose((query) -> this.page(query)).whenComplete((e, r) -> {
			this.finishLoading();
		});
	}

	private void onNextPage() {
		this.$runUICompleteWithContext(() -> {
			return new Query().setTalker(this.chatProperty.get().getId()).setMinID(this.nextStartId);
		}).thenCompose(query -> this.page((Query) query)).whenComplete((e, r) -> {
			this.finishLoading();
		});
	}

	private void finishLoading() {
		this.$setTimeOut(() -> {
			this.loading.finish();
			if (!this.param.hasMore()) {
				this.loading.reset();
			}
		}, 10);
	}

	private final ListChangeListener<? super MessageDTO> messagesChangeListener = this::onMessagesChange;

	@SuppressWarnings("unchecked")
	private void onMessagesChange(Change<? extends MessageDTO> c) {
		while (c.next()) {
			if (c.wasAdded()) {
				List<StackPane> nodes = Lists.newArrayList();
				List<MessageDTO> adds = (List<MessageDTO>) c.getAddedSubList();
				for (MessageDTO chat : adds) {
					this.onAddChat(nodes, chat, false);
				}
				this.messages.getChildren().addAll(0, nodes);
				this.loading.attach();
			} else if (c.wasRemoved()) {
				List<MessageDTO> adds = (List<MessageDTO>) c.getRemoved();
				for (MessageDTO chat : adds) {
					this.onRemoveChat(chat);
				}
			}
		}
	}

	private void onAddChat(List<StackPane> nodes, MessageDTO message, boolean newest) {

		// 只能添加一次
		if (this.hasMessage(message)) {
			return;
		}

		// 添加到列表
		if (newest) {
			this.onAddNewestChat(nodes, message);
		} else {
			this.onAddOldChat(nodes, message);
		}
	}

	private LocalDateTime lastTimeStamp = null; // 新数据的更新
	private LocalDateTime lastShowTimeStamp = null; // 旧数据的更新
	private final long lastShowTimeStampThresholdMinutes = 5;

	private void onAddNewestChat(List<StackPane> nodes, MessageDTO message) {

		if (this.lastTimeStamp == null || DateTimes.betweenTwoTimeSmart(this.lastTimeStamp, message.getCreateDate(),
				ChronoUnit.MINUTES) > this.lastShowTimeStampThresholdMinutes) {
			this.lastTimeStamp = message.getCreateDate();
			nodes.add(new TimeDivide(this.lastTimeStamp));
		}

		nodes.add(this.createMessageNode(message));
	}

	private void onAddOldChat(List<StackPane> nodes, MessageDTO message) {
		if (this.lastShowTimeStamp == null || DateTimes.betweenTwoTimeSmart(this.lastShowTimeStamp,
				message.getCreateDate(), ChronoUnit.MINUTES) > this.lastShowTimeStampThresholdMinutes) {
			this.lastShowTimeStamp = message.getCreateDate();
			nodes.add(new TimeDivide(this.lastShowTimeStamp));
		}

		if (this.lastTimeStamp == null || this.lastTimeStamp.isBefore(this.lastShowTimeStamp)) {
			this.lastTimeStamp = this.lastShowTimeStamp;
		}

		nodes.add(this.createMessageNode(message));
	}

	private StackPane createMessageNode(MessageDTO message) {
		StackPane inner = new StackPane();
		inner.getStyleClass().add(CHAT_STYLE_CLASS);
		MessageComp messageComp = MessageComp.New(MessageComp.class, this);
		messageComp.messageProperty().set(message);
//		if (this.chatroomProperty.get() != null) {
//			messageComp.showNicknameProperty().bind(enableChildRemoveAnimation);
//		}
		inner.getChildren().add(messageComp.getRoot());
		inner.getProperties().put(UI.Conts.ID_Key, message.getId());
		inner.getProperties().put(UI.Conts.Data_Key, message);
		inner.getProperties().put(UI.Conts.Comp_Key, messageComp);

		// 保存列表实例
		this.saveMessageNode(message, inner);

		// 返回实例
		return inner;
	}

	private void onRemoveChat(MessageDTO msg) {
		StackPane node = this.findMessageNode(msg);
		if (node != null) {
			MessageComp messageThumb = (MessageComp) node.getProperties().get(UI.Conts.Comp_Key);
			messageThumb.destory();
			this.messages.getChildren().remove(node);
		}
	}

	private void scrollToBottom() {
		this.$setTimeOut(() -> {
			this.$runUI(() -> {
				this.chatsScroll.scrollToBottom();
			});
		}, 10);
	}

	private void addNewestMessage(MessageDTO message) {
		this.$runUI(() -> {
			List<StackPane> nodes = Lists.newArrayList();
			this.onAddChat(nodes, message, true);
			this.messages.getChildren().addAll(nodes);
		});
		this.scrollToBottom();
	}

	// ================================================================================
	// message <==> Pane
	// ================================================================================
	private Map<Long, StackPane> messagePanes = Maps.newHashMap();

	private boolean hasMessage(MessageDTO chat) {
		return messagePanes.containsKey(chat.getId());
	}

	private void saveMessageNode(MessageDTO chat, StackPane inner) {
		this.messagePanes.put(chat.getId(), inner);
	}

	private StackPane findMessageNode(MessageDTO chat) {
		return this.messagePanes.get(chat.getId());
	}

	// ================================================================================
	// Load Data
	// ================================================================================
	@Getter
	@Setter
	@Accessors(chain = true)
	private class Query {
		private Long minID;
		private String talker;
	}

	private volatile Long nextStartId;
	private volatile Parameters param = new Parameters();

	private CompletableFuture<Void> page(Query query) {
		if (LOGGER.isDebugEnabled()) {
			LOGGER.debug("[聊天内容] - 准备加载数据");
		}
		return this.messageApi
				.page(MessageDTO.of().setId(query.getMinID()).setTalker(query.getTalker()), new Parameters())
				.thenCompose(page -> this.paged(query, page));
	}

	private CompletableFuture<Void> paged(Query query, Page<MessageDTO> page) {

		// 查询的数据
		List<MessageDTO> messages = page.getData();
		if (LOGGER.isDebugEnabled()) {
			LOGGER.debug("[聊天内容] - 加载数据: {}", messages.size());
		}

		// 重新排序 -- 从小到大
		Lists.sort(messages, (m1, m2) -> {
			return m1.getId().compareTo(m2.getId());
		});

		// 查询的数据总数
		this.param = page.getParam();
		this.nextStartId = messages.size() > 0 ? messages.get(0).getId() : Long.MIN_VALUE;

		// 开启一个新的任务
		CompletableFuture<Void> addFuture = new CompletableFuture<>();

		// 这里保证Messages数据和查询出来的结果是一致的顺序
		this.$runUI(() -> {

			// 添加数据
			this.lastShowTimeStamp = null;
			this.messagesProperty().addAll(messages);

			// 完成任务
			addFuture.complete(null);
		});

		// 返回此任务
		return addFuture;
	}

	// ================================================================================
	// Properties
	// ================================================================================
	private final ListProperty<MessageDTO> messagesProperty = new SimpleListProperty<>(
			FXCollections.observableArrayList());

	public ListProperty<MessageDTO> messagesProperty() {
		return messagesProperty;
	}

	private final ObjectProperty<ChatDTO> chatProperty = new SimpleObjectProperty<>();

	public ObjectProperty<ChatDTO> chatProperty() {
		return chatProperty;
	}

	private final StringProperty initProperty = new SimpleStringProperty();
	private final ObjectProperty<ChatRoomDTO> chatroomProperty = new SimpleObjectProperty<>();
	private final ObjectProperty<ContactDTO> contactProperty = new SimpleObjectProperty<>();
	private final ListProperty<ContactDTO> contactsProperty = new SimpleListProperty<>(
			FXCollections.observableArrayList());
}